// =============================================================================
/**
   @file 
   Defines the fundamental @p call_center class
*/


// =============================================================================
//                                 C++ Headers
#include <string>
#include <sstream>
#include <time.h>

// =============================================================================
//                                 3rds Headers


// =============================================================================
//                                 Our Headers
#include "logger.h"
#include "call.h"

// =============================================================================
//                                 Namespaces
using namespace std;



// =============================================================================
//                                 Macro Contants

/**
 */


// =============================================================================
//                                 Macro Commands


/**
   namespace of the organization
*/

namespace tenacitas {
    /**
       namespace for the project
    */
    namespace distributed {
        // =====================================================================
        //                             Typedefs

        /**
         */


        // =====================================================================
        //                             Pre-Declarations


        // =====================================================================
        //                             Attributes

        /**
         */


        // =====================================================================
        //                             Functions

        /**
         */


        // =====================================================================
        //                             Classes
        /**
           @p call_center is responsible for receiving calls from customers, and
           trying to allocate an agent to talk to the customer, who will provide 
           some service. If no agent is available, the call will be on hold, and
           other attempts to allocate an agent will eventually be made. 

           call_center is type parameterized in order to let it highly
           customizable, without losing performance.

           -----------------------------------------------------------------
           @tparam t_network is the physical media through which the calls
           arrive.
           It must implement:

           A type that defines the type of pointer of the @p t_network
           <tt>typedef ptr_type</tt>

           A method that starts the @p t_nerwork to wait for calls in a
           specified address
           <tt>void start( const t_address & )</tt>
               
           A method that waits for a call from a customer at the address
           defined in @p start
           <tt>t_call wait( )</tt>

           A method that makes the network stop receiving calls
           <tt>void stop( )</tt>

           A method that will send a ciphered message to the client
           <tt>void send( const string & )</tt>

           A method that will receive a ciphered message from the client
           <tt>string receive( )</tt>

           -----------------------------------------------------------------
           @tparam t_network_timeout defines how the network will deal with
           delays in sending and receiving messages 

           -----------------------------------------------------------------
           @tparam t_address is the address where a customer should call to, in
           order to reach the call center 

           -----------------------------------------------------------------
           @tparam t_on_hold_calls is responsible for keeping the calls that were
           not answered by an agent yet

           // -----------------------------------------------------------------
           @tparam t_message_cipher is responsible for ciphering and deciphering
           the messages that are received and sent in a call 

           -----------------------------------------------------------------
           @tparam t_message_coder is responsible for coding and decoding the
           messages that are received and sent in a call 

           -----------------------------------------------------------------
           @tparam t_message is the original message sent by the customer.
           It must implement:

           Copy constructor
           <tt>t_message( const t_message & )</tt>

           Assignment operator
           <tt>t_message & operator= ( t_message & );</tt>

           -----------------------------------------------------------------
           @tparam t_agent_allocator is responsible for allocating an @p t_agent
           to serve the customer.
           It must implement:

           A type that defines the type of pointer of the @p t_agent_allocator.
           <tt>typedef ptr_type;</tt>

           A method that starts the @p t_agent_allocator
           <tt>void start( )</tt>

           A method that makes the agent allocator stop allocating any agents
           <tt>void stop( );</tt>

           -----------------------------------------------------------------
           @tparam t_agent is reponsible for attending the call, receiving and 
           (possibly) sending messages, in order to provide a service to the
           customer.

           -----------------------------------------------------------------
           @tparam t_agent_timeout defines how the agents will handle with delays
           in receiving messages
           
           -----------------------------------------------------------------
           @tparam t_call_recorder  is responsible for recording events that
           might occurr in a call. 

           It must implement:
           <tt>
           A type that defines the type of pointer of the logger.
           typedef ptr_type;

           A method that will log, if the log level is debug, a @p p_msg,
           identifying that the log happened at line @p p_line, at file @p
           p_file, in function @p p_function. 
           <tt>void debug( const char * p_file, const char * p_function,
           uint32_t p_line, const char * p_msg );</tt>

           A method that will log, if the log level is warning, a @p p_msg,
           identifying that the log happened at line @p p_line, at file @p
           p_file, in function @p p_function.    
           <tt>void warn( const char * p_file, const char * p_function,
           uint32_t p_line, const char * p_msg );</tt>

           A method that will log, if the log error is error, a @p p_msg,
           identifying that the log happened at line @p p_line, at file @p
           p_file, in function @p p_function.    
           <tt>void error( const char * p_file, const char * p_function,
           uint32_t p_line, const char * p_msg );</tt>

           A method that will log a @p p_msg identifying that the log
           happened at line @p p_line, at file @p p_file, in function @p
           p_function. After logging a message in this level, the @p
           call_center will stop working. 
           <tt>void critical( const char * p_file, const char * p_function,
           uint32_t p_line, const char * p_msg );</tt>




           When a @p  call_center object is created, it is in the @p stopped
           state. In order to make it work, one must call the @p start method. If
           it becomes necessary to put it back in the @p stopped state, it is
           necessary to call the @p stop method.

           If it is desired to change the @p t_address where the @p
           call_center receives calls, call the @p address method. It will
           call @p stop the @p  call_center, set a new @p t_address, and call
           @p start again. 
        */
        template <typename t_network,
                  typename t_network_timeout,
                  typename t_address,
                  typename t_on_hold_calls,
                  typename t_message_cipher,
                  typename t_message_coder,
                  typename t_message,
                  typename t_agent_allocator,
                  typename t_agent,
                  typename t_agent_timeout,
                  typename t_call_recorder>
        class  call_center {
        public:
            // -----------------------------------------------------------------
            //     call_center - Public Internal Types
            
            typedef t_network network_type;

            typedef t_network_timeout network_timeout_type;

            typedef typename network_timeout_type::ptr_type network_timeout_ptr_type;

            /** Type of pointer for the @p network_type */
            typedef typename network_type::ptr_type network_ptr_type;

            typedef t_address address_type;

            typedef call<address_type> call_type;

            typedef t_on_hold_calls on_hold_calls_type;

            /** Type of pointer for the @p on_hold_calls_type */
            typedef typename on_hold_calls_type::ptr_type on_hold_calls_ptr_type; 

            typedef t_message_cipher message_cipher_type;
            
            typedef typename message_cipher_type::ptr_type
            message_cipher_ptr_type; 
            
            typedef t_message_coder message_coder_type;

            typedef typename message_coder_type::ptr_type message_coder_ptr_type; 

            typedef t_message message_type;

            typedef t_agent_allocator agent_allocator_type;

            /** Type of pointer for the @p agent_allocator_type */
            typedef typename agent_allocator_type::ptr_type
            agent_allocator_ptr_type; 

            typedef t_agent_timeout agent_timeout_type;

            typedef typename agent_timeout_type::ptr_type agent_timeout_ptr_type;
            
            typedef t_agent agent_type;

            typedef t_call_recorder call_recorder_type;

            /** Type of pointer for the @p logger_type */
            typedef typename call_recorder_type::ptr_type call_recorder_ptr_type;

    
            // -----------------------------------------------------------------
            //     call_center - Public Constructors
        
            /** Constructor
                @param [in] p_network is the physical media through which the
                calls  arrive

                @param [in] p_network_timeout defines how the network will deal
                with delays in sending and receiving messages

                @param [in] p_on_hold_calls is responsible for keeping the calls
                that were not answered by an agent yet

                @param [in] p_message_cipher is responsible for ciphering and
                deciphering the messages that are received and sent in a call

                @param [in] p_message_coder is responsible for coding and
                decoding the messages that are received and sent in a call

                @param [in] p_agent_allocator is responsible for allocating
                an agent to, well, attend the call

                @param [in] p_address is the address where the clients call the
                assistance center 

                @param [in] p_agent_timeout defines how the agents will handle
                with delays in receiving messages

                @param [in] p_call_recorder is responsible for recording events
                that might occurr in a call
            */
            call_center( network_ptr_type & p_network,
                         network_timeout_ptr_type & p_network_timeout,
                         on_hold_calls_ptr_type & p_on_hold_calls,
                         message_cipher_ptr_type & p_message_cipher,
                         message_coder_ptr_type & p_message_coder,
                         agent_allocator_ptr_type & p_agent_allocator,
                         const address_type & p_address,
                         agent_timeout_ptr_type & p_agent_timeout,
                         call_recorder_ptr_type & p_call_recorder );
                

            /** Destructor
                Stops the call center
            */
            ~call_center( );
                
            // -----------------------------------------------------------------
            //     call_center - Public Accessors

            /** Identifies if the call center is stopped */
            bool stopped( ) const;

            /** Identifies if the call center has started */
            bool started( ) const;

            /** Defines a new address for the call center to receive calls

                This will make the call center to stop, and starting again
            */
            void address( const address_type & p_new_address );
                

            // -----------------------------------------------------------------
            //     call_center - Public Helpers
            

            // -----------------------------------------------------------------
            //     call_center - Public Processors
            
            /** 
                Starts call center

                Calls are accepted, and forwarded to the @p call_handler_type
                defined */
            void start( );
                

            /**
               Stops call center
            */
            void stop( );
                

            // -----------------------------------------------------------------
            //     call_center - Public Operators
            

            // -----------------------------------------------------------------
            //     call_center - Public Attributes

        private:

            // -----------------------------------------------------------------
            //     call_center - Private Internal Types

            /** The possible states of the  call_center */
            enum enum_state { stopped_state, started_state };

            typedef enum enum_state state;
            
                    

            // -----------------------------------------------------------------
            //     call_center - Private Processors

            /** Tries to start all components for the call center to operate */
            bool start_components( );

            /** Tries to stop all components for the call center to operate */
            bool stop_components( );

            /** Handles an incoming call */
            void handle( call_type & p_call );

            // -----------------------------------------------------------------
            //     call_center - Private Attributes

            /** physical media through which the calls arrive */     
            network_ptr_type m_network;

            /** how the network will deal with delays in sending and receiving
                messages */ 
            network_timeout_ptr_type m_network_timeout;

            /** keeps the calls that were not answered by an agent yet */
            on_hold_calls_ptr_type m_on_hold_calls;

            /** ciphers and deciphering the messages that are received and sent
                in a call */
            message_cipher_ptr_type m_message_cipher;

            /** codes and decoding the messages that are received and sent in a
                call */
            message_coder_ptr_type m_message_coder;

            /** responsible for allocating an agent to, well, attend the call */
            agent_allocator_ptr_type m_agent_allocator;

            /** address where the clients call the assistance center */
            const address_type m_address;

            /** how the agents will handle with delays in receiving messages */
            agent_timeout_ptr_type m_agent_timeout;
            
            /** responsible for logging messages */
            call_recorder_ptr_type & m_call_recorder;

            /** indicates the current state of the call center */
            state m_state;

            // to format log messages
            std::stringstream m_stream;
            
        };


    }
}


// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::distributed;


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
call_center
( typename t_network::ptr_type & p_network,
  typename t_network_timeout::ptr_type & p_network_timeout, 
  typename t_on_hold_calls::ptr_type & p_on_hold_calls, 
  typename t_message_cipher::ptr_type & p_message_cipher, 
  typename t_message_coder::ptr_type & p_message_coder, 
  typename t_agent_allocator::ptr_type & p_agent_allocator, 
  const t_address & p_address,
  typename t_agent_timeout::ptr_type & p_agent_timeout, 
  typename t_call_recorder::ptr_type & p_call_recorder ) 
    : m_network( p_network ),
      m_network_timeout( p_network_timeout ),
      m_on_hold_calls( p_on_hold_calls ),
      m_message_cipher( p_message_cipher ),
      m_message_coder( p_message_coder ),
      m_agent_allocator( p_agent_allocator ),
      m_address( p_address ),
      m_agent_timeout( p_agent_timeout ),
      m_call_recorder( p_call_recorder ),
      m_state( stopped_state ) {}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
~call_center( ) {
    log_in( m_call_recorder );

    m_stream.str( "" );

    try {
        stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error destroying call_center: " << l_exception.what( );
        log_critical ( m_call_recorder, m_stream.str( ).c_str( ) );
    }

    log_out( m_call_recorder );
}

// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
bool
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
stopped( ) const {return ( m_state == stopped_state );}

// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
bool
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
started( ) const {return ( m_state == started_state );}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
bool
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
start_components( ) {
    log_in( m_call_recorder );

    // hoping for the best
    bool l_rc = true;
    
    // cleaning the log message formater
    m_stream.str( "" );

    try {
        // trying to start the network timeout stratergy
        m_network_timeout->start( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting network timeout stratergy: " <<
            l_exception.what( ); 
        goto l_exit;
    }

    try {
        // trying to start the network, informing that it should expect calls at
        // m_address, and that its timeout policy is defined in m_network_timeout
        m_network->start( m_address, m_network_timeout );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting network: " << l_exception.what( );
        goto l_exit;
    }

    try {
        // trying to start the agent allocator
        m_agent_allocator->start( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting agent allocator: " << l_exception.what( ); 
        goto l_exit;
    }

    try {
        // trying to start the on hold calls handler
        m_on_hold_calls->start( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting the on-hold calls handler: " <<
            l_exception.what( ); 
        goto l_exit;
    }

    try {
        // trying to start the cipher/decipher component
        m_message_cipher->start( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting the cipher/decipher component: " <<
            l_exception.what( ); 
        goto l_exit;
    }

    try {
        // trying to start the coder/decoder component
        m_message_coder->start( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error starting the coder/decoder component: " <<
            l_exception.what( ); 
        goto l_exit;
    }
    

  l_exit:
    if ( m_stream.str( ).size( ) ) {
        log_critical( m_call_recorder, m_stream.str( ) );
        l_rc = false;
    }

    log_out( m_call_recorder );

    return l_rc;
}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
bool
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
stop_components( ) {
    log_in( m_call_recorder );

    // hoping for the best
    bool l_rc = true;
    
    // cleaning the log message formater
    m_stream.str( "" );

    try {
        // trying to stop the network
        m_network->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stoping the network: " << l_exception.what( );
        
        log_critical( m_call_recorder, m_stream.str( ) );
    }

    try {
        // trying to stop the network timeout stratergy
        m_network_timeout->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stoping network timeout stratergy: " <<
            l_exception.what( );

        log_critical( m_call_recorder, m_stream.str( ) );
    }


    try {
        // trying to stop the agent allocator
        m_agent_allocator->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stoping agent allocator: " << l_exception.what( ); 

        log_critical( m_call_recorder, m_stream.str( ) );
    }

    try {
        // trying to stop the on hold calls handler
        m_on_hold_calls->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stop the on-hold calls handler: " <<
            l_exception.what( ); 

        log_critical( m_call_recorder, m_stream.str( ) );
    }

    try {
        // trying to stop the cipher/decipher component
        m_message_cipher->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stop the cipher/decipher component: " <<
            l_exception.what( ); 

        log_critical( m_call_recorder, m_stream.str( ) );
    }

    try {
        // trying to stop the coder/decoder component
        m_message_coder->stop( );
    }
    catch ( std::exception & l_exception ) {
        m_stream << "Error stop the coder/decoder component: " <<
            l_exception.what( ); 

        log_critical( m_call_recorder, m_stream.str( ) );

    }

    if ( m_stream.str( ).size( ) ) {
        l_rc = false;
    }
    
    log_out( m_call_recorder );

    return l_rc;
}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
void
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
start( ) {
    log_in( m_call_recorder );

    // cleaning the log message formater
    m_stream.str( "" );

    // stopping the call center
    log_debug( m_call_recorder, "Stopping the call_center" );    
    stop( );

    if ( !stopped( ) ) {
        log_critical( m_call_recorder, "Error stopping call_center" );
        goto l_exit;
    }

    if ( !start_components( ) ) {        
        log_critical( m_call_recorder, "Error starting components for the "
                      "call center" );
        goto l_exit;
    }

    // ok!  call_center has started
    m_state = started_state;
    log_debug( m_call_recorder, "call_center started" );

    // "Who wants to live forever?", by Queen
    while ( true ) {
        // someone asked the call center to stop
        if ( stopped( ) ) { break; }

        // the call to be received 
        call_type l_call;

        try {
            // wait forever (according to the m_network_timeout, for a call at
            // m_address 
            l_call = m_network->wait( );
            l_call.received( time( 0 ) );
        }
        catch ( std::exception & l_exception ) {
            m_stream << "Error receiving call: " << l_exception.what( );
            goto l_exit;
        }

        try {
            // handling the call
            handle( l_call );
        }
        catch ( std::exception & l_exception ) {
            m_stream << "Error handling call: " << l_exception.what( );
            goto l_exit;
        }
    }


  l_exit:

    if ( m_stream.str( ).size( ) ) {
        // we are exiting because something went very wrong 
        log_critical( m_call_recorder, m_stream.str( ).c_str( ) );
    }
    
        
    if ( !stopped( ) ) {
        stop( );
        if ( !stopped( ) ) {
            log_critical( m_call_recorder, "Error stopping call_center" );
        }
    }
    
    
    log_out( m_call_recorder );
}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
void
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
handle( call_type & p_call ) {
    log_in( m_call_recorder );

    // AQUI
    
    log_out( m_call_recorder );
}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
void
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
stop( ) {
    log_in( m_call_recorder );

    // cleaning the log message formater
    m_stream.str( "" );

    if ( !started( ) ) {
        log_warn( m_call_recorder, "Attempt to stop call_center when it "
                  "was not started" );
        goto l_exit;
    }

    if ( !stop_components( ) ) {
        log_critical( m_call_recorder, "Error stoping components" );
        goto l_exit;
    }
    

    // if we got this far, it is because all the components were stopped 
    m_state = stopped_state;


  l_exit:
    log_out( m_call_recorder );    
}


// -----------------------------------------------------------------------------
template<typename t_network,
         typename t_network_timeout,
         typename t_address,
         typename t_on_hold_calls,
         typename t_message_cipher,
         typename t_message_coder,
         typename t_message,
         typename t_agent_allocator,
         typename t_agent,
         typename t_agent_timeout,
         typename t_call_recorder>
void
call_center<t_network,
            t_network_timeout,
            t_address,
            t_on_hold_calls,
            t_message_cipher,
            t_message_coder,
            t_message,
            t_agent_allocator,
            t_agent,
            t_agent_timeout,
            t_call_recorder>::
address( const address_type & p_new_address ) {
    log_in( m_call_recorder );
    
    stop( );

    if ( !stopped( ) ) {
        log_critical( m_call_recorder, "Failing to stop the  call_center" );
    }
    else {
        m_address = p_new_address;
        start( );
        if ( !started( ) ) {
            log_critical( m_call_recorder, "Failing to start the  call_center" );
        }
    }

    log_out( m_call_recorder );
}
